<?php
/**
 * Calendar Class for ACC Mtl web site (use to google ics calendar)
 *
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author     Stephane Chamberland <dokuwiki@meteokid.ca>
 * @date       2009-02-22
 */

// must be run within Dokuwiki
if(!defined('DOKU_INC')) die();
if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
if(!defined('DOKU_PLUGIN_ICS_SHOW')) define('DOKU_PLUGIN_ICS_SHOW',DOKU_PLUGIN.'icsshow/');

require_once(DOKU_PLUGIN_ICS_SHOW.'ACC_Cache.class.php');

/**
 * Basic class to decode remode ics calendar
 */
class ACC_Calendar {

    public  $old2new = true; //should be const
    public  $new2old = false; //should be const

    public  $no_date_min = 0; //should be const
    public  $no_date_max = 9999999999999; //should be const

    protected $sort_order = true;

    protected $cal_dir = '';
    protected $cal_array = array();
    protected $cals_URL = array();

    protected $cacheObj = NULL;

    protected $date_min = false;
    protected $date_max = false;

    protected $renderObj = false;
    protected $shortlen = 200;
    protected $shortsep = '----';

    /**
     *
     */
    public function __construct($cal_dir,$cache_dir,$cache_timeout=21600) {
        //TODO: check that provided dir are valid and writable
        $this->cal_dir = $cal_dir;
        $this->cacheObj = new ACC_Cache($cache_dir,$cache_timeout);
        $this->date_min = $this->no_date_min;
        $this->date_max = $this->no_date_max;
    }


    /**
     *
     */
    public function setRenderObj($obj){
        $this->renderObj = $obj;
    }

    /**
     *
     */
    public function setShortDescLen($len=200,$sep='----'){
        $this->shortlen = $len;
        $this->shortsep = $sep;
    }

    /**
     *
     */
    public function set_cache_timeout($timeout) {
        $this->cacheObj->setTimeout($timeout);
        $this->updateCals();
    }

    /**
     *
     */
    public function addCals($cals_URL) {
        //TODO: make sure calendars of the same name do not overwrite previous ones
        if(is_array($cals_URL)) {
            $this->cals_URL = array_merge($this->cals_URL,$cals_URL);
            $this->updateCals();
        }
    }

    /**
     *
     */
    protected function postfix($calArray){
        foreach($calArray as $i => $event){
            //Clean up or create basic values
            foreach(array('start_unix','end_unix','summary','location','description','uid') as $k) {
                if(isset($event[$k])){
                    $item = str_replace('\,',',',html_entity_decode(utf8_decode($event[$k])));
                    $item = str_replace(array('<br>','<br/>','<br />'),array("\n","\n","\n"),$item);
                    $event[$k] = htmlspecialchars(trim($item));
                } else {
                    $event[$k] = '';
                }
            }
            if(!$event['uid']) $event['uid'] = $event['start_unix'];
            $desc = $event['description'];
            if(!$desc) $desc = "\n";

            //Extract param from desc if any (-paramname: paravalue\n)
            $msg = '';
            foreach(explode("\n",$desc) as $v){
                if($v[0]=='-' && preg_match('/^(-\s*[a-zA-Z]+):\s*(.+)$/',$v,$m)){
                    $keyname = str_replace(' ','',strtolower($m[1]));
                    $event[$keyname] = $m[2];
                } else {
                    $msg .= $v."\n";
                }
            }
            $event['description_noparam'] = $msg;
            $desc = $msg;

            //extract short desc
            $msg = '';
            foreach(explode("\n",$desc) as $v){
                if(!$this->shortlen || strlen($msg) < $this->shortlen) {
                    if($this->shortsep !== false && $v == $this->shortsep) break;
                    $msg .= trim($v)."\n";
                } else {
                    break;
                }
            }
            $event['description_short'] = $msg;

            //render
            if($this->renderObj) $calArray[$i]['xhtml'] = array();
            foreach($event as $k=>$v) {
                $calArray[$i][$k] = $v;
                if($this->renderObj)
                    $calArray[$i]['xhtml'][$k] = $this->renderObj->render($v);
            }
        }
        return $calArray;
    }

    /**
     *
     */
    public function updateCals(){
        foreach($this->cals_URL as $name => $calURL) {
            $calArray   = false;
            $calFileStr = $this->cacheObj->getFile($name.'.ics.txt');
            if($calFileStr === false) {
                if(!$this->_upload($calURL,$name.'.ics.part')) {
                    msg('Problem uploading ICS file, trying to use cached version ['.$name.']:'.$calURL,-1);
                    $calFileStr = $this->cacheObj->getFile($name.'.ics.txt',$this->cacheObj->NOTIMEOUT);
                    if($calFileStr !== false) $calArray = unserialize($calFileStr);
                } else {
                    if(@file_exists($this->cal_dir.$name.'.ics.part')){
                        @unlink($this->cal_dir.$name.'.ics');
                        @rename($this->cal_dir.$name.'.ics.part', $this->cal_dir.$name.'.ics');
                        if(@file_exists($this->cal_dir.$name.'.ics')){
                            $calArray = $this->_parseICS($name);
                            if(is_array($calArray)) {
                                $calArray = $this->postfix($calArray);
                                @$this->cacheObj->saveFile($name.'.ics.txt',serialize($calArray));
                            }
                        }
                    }
                }
            } else {
                $calArray = unserialize($calFileStr);
            }
            if(is_array($calArray)) {
                unset($calArray[0]);
                $this->cal_array = array_merge($this->cal_array,$calArray);
            }
        }

        $sortarray = array();
        foreach($this->cal_array as $i => $event) {
            $sortarray['_'.$i] = $event['start_unix'];
        }
        asort($sortarray);

        $trimCalArray = array();
        foreach($sortarray as $k => $v) {
            $i = substr($k,1);
            $trimCalArray[] = $this->cal_array[$i];
        }
        $this->cal_array = $trimCalArray;
        if($this->sort_order == $this->new2old) $this->cal_array = array_reverse($this->cal_array);
    }


    /**
     *
     */
    public function set_sort_order($order) {
        if($this->sort_order != $order)
            $this->cal_array = array_reverse($this->cal_array);
        $this->sort_order = $order;
    }

    /**
     *
     */
    public function date_modifier($datemod){
        $diff = 0;
        $datemod = trim($datemod);
        if($datemod && ($datemod[0] == '+' || $datemod[0] == '-')){
            $mod2 = trim(substr($datemod,1));
            if(is_numeric($mod2)) {
                $diff = $mod2;
                if($datemod[0]=='-') $diff = -1 * $mod2;
            }
        }
        //msg('datemode:'.$datemod.":".$datemod[0].":".$mod2.":".$diff,0);
        return $diff;
    }

    /**
     *
     */
    public function date_decode($date,$max=false){
        $date = strtolower(trim($date));
        switch(substr($date,0,3)){
            case 'now':
                $time = time() + 60*60*$this->date_modifier(substr($date,3));
                break;
            case 'tod'://today
                $time = mktime(0, 0, 0, date("m")  , date("d"), date("Y"))
                     + 60*60*24*$this->date_modifier(substr($date,5));
                if($max) $time += 60*60*24;
                break;
            default:
                $time = strtotime($date);
        }
        return $time;
    }


    /**
     *
     */
    public function set_range($date_min,$date_max){
        //msg('ACC_calendar::set_range:'.$date_min.":".$date_max,0);
        $time = $this->date_decode($date_min);
        if($time) $this->date_min = $time;
        $time = $this->date_decode($date_max,true);
        if($time) $this->date_max = $time;
        //msg('set_range:'.$date_min.'['.date('Ymd',$this->date_min).']:'.$date_max.'['.date('Ymd',$this->date_max).']',0);
    }


    /**
     *
     */
    public function get_cal_array(){
        $cal2 = array();
        foreach($this->cal_array as $event){
            if ($event['start_unix'] >= $this->date_min && $event['end_unix'] < $this->date_max) $cal2[] = $event;
        }
        return $cal2;
    }


    /**
     *
     */
    public function get_i0($mycal,$date0){
        if($date0===false) return 0;
        if(count($mycal)<=1) return 0;
        $time0 = $this->date_decode($date0);
        $order = $this->old2new;
        if ($mycal[0]['start_unix'] > $mycal[count($mycal)-1]['start_unix'])
            $order = $this->new2old;
        if($order == $this->old2new){
            foreach($mycal as $i => $event){
                if ($event['start_unix'] >= $time0) return $i;
            }
        } else { //new2old
            foreach($mycal as $i => $event){
                if ($event['end_unix'] <= $time0) return $i;
            }
        }
        return count($this->cal_array)-1;
    }


    /**
     *
     */
    protected function get_iRange($mycal,$date0,$nb_items=false,$page=0) {
        $i0a = $this->get_i0($mycal,$date0);
        $i0 = $i0a;
        $in = count($mycal);
        if($nb_items){
            $i0 += $page*$nb_items;
            $in = min($i0 + $nb_items,$in);
        }
        return array($i0,$in);
    }


    /**
     *
     */
    protected function findId($mycal,$uid){
        foreach($mycal as $i => $event){
            if($event['uid']==$uid) return $i;
        }
    }

    /**
     *
     */
    protected function _upload($calURL,$name) {
        require_once(DOKU_INC.'inc/io.php');
        $name2=$this->cal_dir.$name;
        return io_download($calURL,$name2);
    }

    /**
     *
     */
    protected function _parseICS($name) {
        require_once(DOKU_PLUGIN_ICS_SHOW.'parse_ics.php');
        return parse_ical($this->cal_dir.$name);
    }

}




/**
 * Basic class to render and decode remode ics calendar
 */
class ACC_Calendar_HTML extends ACC_Calendar {

    protected $format = "<div class='@@odd@@' style='border:1px solid;'>@start_date@ : @summary@ <br />Location: @location@<br />@description@</div>";
    protected $defaultTagsValue = array();
    protected $dateformat = '%Y-%m-%d';
    protected $xhtml = false;


    /**
     *
     */
    public function setFormat($format,$defaultTagsValue=array()){
        $this->format = $format;
        $this->defaultTagsValue = $defaultTagsValue;
    }

    /**
     *
     */
    public function setDateFormat($format){
        $this->dateformat = $format;
    }


    /**
     *
     */
    protected function _get_event_data($event) {
        if(!is_array($event)) return $event;
        $event['start_date'] = strftime($this->dateformat,$event['start_unix']);
        $event['start_date_year'] = strftime('%Y',$event['start_unix']);
        $event['start_date_month'] = strftime('%b',$event['start_unix']);
        $event['start_date_day'] = strftime('%d',$event['start_unix']);
        $event['end_date'] = strftime($this->dateformat,$event['end_unix']);
        $event['end_date_year'] = strftime('%Y',$event['end_unix']);
        $event['end_date_month'] = strftime('%b',$event['end_unix']);
        $event['end_date_'] = strftime('%m',$event['end_unix']);
        //TODO: translate month string
        //TODO: allow different format for day,month,year
        foreach($event as $k=>$v) {
            if(!is_array($v)){
                if($this->xhtml && isset($event['xhtml']) && isset($event['xhtml'][$k])) {
                    $event[$k] = $event['xhtml'][$k];
                } else if(strpos($k,'description')!==false) {
                    $event[$k] = str_replace("\n",'<br />',$event[$k]);
                }
            }
        }
        return $event;
    }

    /**
     *
     */
    public function getPrevNextItemId($id){
        $prevId = false;
        $nextId = false;
        $mycal = $this->get_cal_array();
        $ii = $this->findId($mycal,$id);
        if($ii>0) $prevId = $mycal[$ii-1]['uid'];
        if($ii<count($mycal)) $nextId = $mycal[$ii+1]['uid'];
        return array($prevId,$nextId);
    }

    /**
     *
     */
    public function hasPrevNextPage($date0,$nb_items=false,$page=0){
        $prev = false;
        $next = false;
        $mycal = $this->get_cal_array();
        list($i0,$in) = $this->get_iRange($mycal,$date0,$nb_items,$page);
        if($i0>0) $prev = true;
        if($in<count($mycal)) $next = true;
        return array($prev,$next);
    }

    /**
     *
     */
    public function toStr($date0,$nb_items=false,$page=0){
        $mycal = $this->get_cal_array();
        list($i0,$in) = $this->get_iRange($mycal,$date0,$nb_items,$page);
        $mystr = '';
        for($ii=$i0;$ii<$in;$ii++){
            $event = $this->_get_event_data($mycal[$ii]);
            $event['odd'] = ($ii%2) ? 'odd' : 'even';
            $mystr .= $this->template_replace($this->format,$event,$this->defaultTagsValue);
        }
        return $mystr;
    }

    /**
     *
     */
    public function toStrItem($id){
        $mycal = $this->get_cal_array();
        $ii = $this->findId($mycal,$id);
        $event = $this->_get_event_data($mycal[$ii]);
        return $this->template_replace($this->format,$event,$this->defaultTagsValue);
    }

    /**
     *
     */
    public function template_replace($template_str,$search_replace,$defaults=array()) {
        $search = array();
        $replace = array();
        foreach(array_merge($defaults,$search_replace) as $k=>$v){
            $search[]  = '@@'.$k.'@@';
            $replace[] = $v;
        }
        return str_replace($search,$replace,$template_str);
    }
}
